from ConfigChecker import *
from lazy_judge import *
from smake_db import *
from Utility import *
from cmd_runner import *
from dag import *
import os
import os.path
import shutil
import logging
import hashlib
import copy

# ===CODE===

OBJ_DIR_NAME = 'obj'
OBJPIC_DIR_NAME = 'objpic'
MGC_DIR_NAME = '_mgc'
INC_DIR_NAME = 'include'
SRC_DIR_NAME = 'src'
REMOTE_DIR_NAME = '_remote'
C_FILE_SUFFIX = {'c'}
CXX_FILE_SUFFIX = {'cpp', 'cc', 'cxx'}
SRC_FILE_SUFFIX = C_FILE_SUFFIX.union(CXX_FILE_SUFFIX)

class Builder:
    def __init__(self, conf, args):
        self.conf = conf
        self.args = args
        self.output_dir = args.output_dir
        self.lazy_judge = LazyJudge(args.force_build)
        self.proj_dag = DAG()
        self.c_suffix = {'c'}
        self.cpp_suffix = {'cpp', 'cxx', 'cc'}
        self.files = FileLists()
        self.proj_obj_files = {} # {proj_name:[obj_name]} 本次构建要生成的obj文件
        # self.proj_objpic_files = {} # {proj_name:[objpic_name]}

    def genBuildSolutionCmds(self) -> tuple:
        '''生成构建解决方案所需的所有cmds，
        返回各个项目的构建顺序，以及各个项目构建所用到的cmds
        
        Return: [build_order, build_cmds]'''
        # 更新配置文件的修改情况
        self.__update_conf_md5()

        # 检查配置文件中的项目对应的目录是否存在
        self.__check_proj_dir()

        # 分析项目依赖关系
        for proj_name in self.conf[OPT_PROJECTS]:
            dep_proj_names = self.conf[OPT_PROJECTS][proj_name][OPT_DEPENDENCY]
            self.proj_dag.set_point(proj_name)
            for dep_name in dep_proj_names:
                self.proj_dag.set_edge(dep_name, proj_name)

        # 决定要构建的项目，以及顺序
        proj_build_order = []
        # if self.args.project != '':
        #     raise Exception('--project option not ok')
        # else:
        #     proj_build_order = self.proj_dag.get_topology_sort()
        proj_build_order = self.proj_dag.get_topology_sort()

        # 下载远程项目
        # for proj_name in self.conf[OPT_PROJECTS]:
        #     proj_info = self.conf[OPT_PROJECTS][proj_name]
        #     if proj_info[OPT_REMOTE] != None:
        #         # 判断是否已经下载
        #         pass
        #         # 修改proj_dir

        # 机器生成代码
        # self.__create_mgc_out_dir()
        # for proj_name in proj_build_order:
        #     proj_info = self.conf[OPT_PROJECTS][proj_name]
        #     if proj_info[OPT_BUILD_SCRIPT] != '':
        #         continue
        #     if proj_info[OPT_MGC_SCRIPT] == '':
        #         continue
        #     # 更新mgc目录下的modify_time
        #     proj_dir = proj_info[OPT_PROJ_DIR]
        #     mgc_dir = path_join(proj_dir, 'mgc')
        #     mgc_file_names = get_dir_files(mgc_dir)
        #     self.files.set_mgc_files(proj_name, mgc_file_names)
        #     mgc_file_mtimes = get_files_mtime(mgc_file_names)
        #     g_db.set_file_mtimes(proj_name, mgc_file_mtimes)
        #     # 生成mgc的命令
        #     if self.lazy_judge.need_gen_mgc(proj_name) == True:
        #         logging.info('generate MGC for project %s:'%(proj_name))
        #         mgc_scrip_path = path_join(proj_dir, 'mgc', proj_info[OPT_MGC_SCRIPT])
        #         env = self.__gen_bash_env(proj_name)
        #         sh = proj_info[OPT_BUILD_OPTIONS][OPT_SCRIPT_SHELL]
        #         cmd = '%s %s %s'%(env, sh, mgc_scrip_path)
        #         ret = run_cmd(cmd, False, logging.INFO)
        #         if ret.returncode != 0:
        #             logging.error('generate MGC failed.')
        #             exit(1)
        #     # 记录mgc生成的文件
        #     mgc_inc_dir, mgc_src_dir = self.__get_proj_mgc_dirs(proj_name)
        #     mgc_inc_files = get_dir_files(mgc_inc_dir)
        #     mgc_src_files = get_dir_files(mgc_src_dir)
        #     mgc_src_inner_dirs = get_dir_dirs(mgc_src_dir)
        #     self.files.set_mgc_inc_files(mgc_inc_files)
        #     self.files.set_mgc_src_files(mgc_src_files)
        #     self.files.append_src_inner_dirs(proj_name, mgc_src_inner_dirs)

        # 遍历所有代码文件，更新文件修改时间（不含MGC文件，包含MGC生成的代码）
        for proj_name in self.conf[OPT_PROJECTS]:
            inc_dir, src_dir = self.__get_proj_code_dirs(proj_name)
            inc_file_names = get_dir_files(inc_dir)
            src_file_names = get_dir_files(src_dir)
            src_inner_dirs = get_dir_dirs(src_dir)
            self.files.set_inc_files(proj_name, inc_file_names)
            self.files.set_src_files(proj_name, src_file_names)
            self.files.append_src_inner_dirs(proj_name, src_inner_dirs)
            # 后续还要加入remote的代码
            inc_mtimes = get_files_mtime(inc_file_names)
            src_mtimes = get_files_mtime(src_file_names)
            mgc_inc_mtimes = get_files_mtime(self.files.get_mgc_inc_files(proj_name))
            mgc_src_mtimes = get_files_mtime(self.files.get_mgc_src_files(proj_name))
            g_db.set_file_mtimes(proj_name, inc_mtimes)
            g_db.set_file_mtimes(proj_name, src_mtimes)
            g_db.set_file_mtimes(proj_name, mgc_inc_mtimes)
            g_db.set_file_mtimes(proj_name, mgc_src_mtimes)

        # 遍历所有文件的mtime，判断项目、文件是否修改，并记录
        self.lazy_judge.update_is_change()

        # 更新文件依赖关系
        all_src_file_names, cmds = self.__gen_file_rely_analyze_cmds()
        cmds_ret = run_cmds(cmds, True, logging.DEBUG)
        # ret和all_src_file_names一一对应
        for idx in range(0, len(cmds_ret)):
            ret = cmds_ret[idx]
            if ret.returncode != 0:
                # 对于失败的命令不作处理，留在编译阶段做出明确的提示
                continue
            # 从stdout中截取依赖
            dep_files = ret.stdout.decode('utf-8').strip().split(' ')
            if len(dep_files) <= 2:
                continue
            dep_files = dep_files[2:] # 前两项没用
            file_name = all_src_file_names[idx]
            g_db.set_file_rely(file_name, dep_files)

        # 为所有项目创建产出的目录结构
        self.__create_proj_out_dir()

        # 生成所有构建命令
        proj_cmds = self.__gen_projs_build_cmds(proj_build_order)
        return (proj_build_order, proj_cmds)

    def build(self, build_cmds_info):
        # 执行before_solution脚本
        before_build_solution_script = self.conf[OPT_GLOBAL][OPT_BEFORE_BUILD_SOLUTION_SCRIPT]
        if before_build_solution_script != '':
            env = self.__gen_bash_env()
            sh = self.conf[OPT_BUILD_OPTIONS][OPT_BUILD_SCRIPT]
            cmd = '%s %s %s'%(env, sh, before_build_solution_script)
            ret = run_cmd(cmd, False, logging.INFO)
            if ret.returncode != 0:
                return

        # 执行所有项目的构建命令
        proj_build_order = build_cmds_info[0]
        proj_cmds = build_cmds_info[1] # {proj_name: [(fname, [cmd...])...]}
        for proj_name in proj_build_order:
            # 不需要编译的项目，没有对应的条目
            if proj_name not in proj_cmds:
                g_db.set_proj_success(proj_name, True)
                continue
            # 跳过没有构建cmd的项目
            cmds_group = proj_cmds[proj_name]
            empty_build = True
            for cmds in cmds_group:
                if len(cmds) != 0:
                    empty_build = False
                    break
            if empty_build == True:
                g_db.set_proj_success(proj_name, True)
                logging.log(logging.INFO, '')
                logging.info('skip build project (%s), since nothing changed.'%(proj_name))
                continue

            logging.log(logging.INFO, '')
            logging.log(logging.INFO, 'building project: ' + proj_name)
            g_db.set_proj_success(proj_name, False)
            # 把当前的 proj_cmds 整理成file_name和cmd一一对应的格式
            proj_ok = True
            for item in cmds_group:
                if len(item) == 0:
                    continue
                # 此时item的格式：[(file_name, cmds) ...]
                file_names = list(map(lambda x : x[0], item))
                cmds = list(map(lambda x : x[1], item))
                ret = run_cmds(cmds, False, logging.INFO)
                # 记录所有编译相关命令的结果
                for idx in range(0, len(cmds)):
                    if ret[idx].returncode == 0:
                        if file_names[idx] != '':
                            g_db.set_file_success(file_names[idx], True)
                    else:
                        proj_ok = False
                if proj_ok == False:
                    break
            if proj_ok == True:
                g_db.set_proj_success(proj_name, True)
            else:
                return

        # 执行after_solution脚本
        after_build_solution_script = self.conf[OPT_GLOBAL][OPT_AFTER_BUILD_SOLUTION_SCRIPT]
        if after_build_solution_script != '':
            env = self.__gen_bash_env()
            sh = self.conf[OPT_BUILD_OPTIONS][OPT_BUILD_SCRIPT]
            cmd = '%s %s %s'%(env, sh, after_build_solution_script)
            ret = run_cmd(cmd, False, logging.INFO)
            if ret.returncode != 0:
                return
                
        logging.log(logging.INFO, '')
        logging.log(logging.INFO, '======   OK   ======')
        logging.log(logging.INFO, '')
        return

    def clean_all(self) -> None:
        # 删除output
        if os.path.exists(self.output_dir):
            shutil.rmtree(self.output_dir)

        # 清空mgc生成的代码

        # 执行extra_clean脚本

        logging.log(logging.INFO, '')
        logging.log(logging.INFO, '====== All Cleaned ======')
        logging.log(logging.INFO, '')

    def __check_proj_dir(self) -> None:
        '''检查所有项目的文件夹是否存在，失败直接exit'''
        for proj_name in self.conf[OPT_PROJECTS]:
            proj_info = self.conf[OPT_PROJECTS][proj_name]
            # # 不检查remote项目
            # if proj_info[OPT_REMOTE] != None:
            #     continue
            # 得到项目的路径
            proj_dir = proj_info[OPT_PROJ_DIR]
            if os.path.exists(proj_dir) == False:
                errorExit('project directory not exist. proj_name:%s proj_dir:%s'%(proj_name, proj_dir))
            # 有build_script的检查到此为止
            if proj_info[OPT_BUILD_SCRIPT] != '':
                continue
            # 检查src目录
            if len(set(['lib', 'dll', 'exe', 'mexe']).intersection(proj_info[OPT_TYPE])) > 0:
                src_dir = path_join(proj_dir, SRC_DIR_NAME)
                if os.path.exists(src_dir) == False:
                    errorExit('project src directory not exist: %s'%(proj_name))

    def __create_mgc_out_dir(self):
        '''创建存放mgc生成的代码的目录'''
        dir_names = []
        for proj_name in self.conf[OPT_PROJECTS]:
            # 找到有MGC的项目
            proj_info = self.conf[OPT_PROJECTS][proj_name]
            if proj_info[OPT_BUILD_SCRIPT] != '':
                continue
            if proj_info[OPT_MGC_SCRIPT] != '':
                inc_dir, src_dir = self.__get_proj_mgc_dirs(proj_name)
                dir_names.append(inc_dir)
                dir_names.append(src_dir)
        for name in dir_names:
            os.makedirs(name, exist_ok=True)
        
    def __create_proj_out_dir(self):
        '''为每个项目创建输出目录'''
        os.makedirs(self.output_dir, exist_ok=True)
        os.makedirs(path_join(self.output_dir, REMOTE_DIR_NAME), exist_ok=True)
        dir_names = []
        for proj_name in self.conf[OPT_PROJECTS]:
            # 项目产出和目标文件
            # mgc的src编译产生的目标文件也放在obj目录中
            proj_obj_dir = path_join(self.output_dir, proj_name, OBJ_DIR_NAME)
            proj_objpic_dir = path_join(self.output_dir, proj_name, OBJPIC_DIR_NAME)
            dir_names.append(path_join(self.output_dir, proj_name))
            dir_names.append(proj_obj_dir)
            dir_names.append(proj_objpic_dir)
            # # obj目录要与src目录保持目录结构一直【暂时不这么做了，过于复杂】
            # dirs = self.files.get_src_inner_dirs(proj_name)
            # for name in dirs:
            #     dir_name = path_join(proj_obj_dir, name)
            #     dir_names.append(dir_name)
            #     dir_name = path_join(proj_objpic_dir, name)
            #     dir_names.append(dir_name)
            
        for name in dir_names:
            os.makedirs(name, exist_ok=True)

    def __gen_bash_env(self, proj_name=None) -> str:
        '''为项目内的脚本生成环境变量
        
        Args:
            proj_name: 项目名称，如果为None，则不生成项目相关的内容
        Return: 返回一个K=V格式的字符串，用空格间隔'''
        env = {}
        env['SOLUTION_ABS_DIR'] = os.getcwd()
        env['OUTPUT_DIR'] = self.output_dir
        if proj_name != None:
            proj_info = self.conf[OPT_PROJECTS][proj_name]
            proj_dir = proj_info[OPT_PROJ_DIR]
            env['PROJ_NAME'] = proj_name
            env['PROJ_DIR'] = proj_dir
            env['PROJ_OUT_DIR'] = path_join(self.output_dir, proj_name)
            env['PROJ_OBJ_DIR'] = path_join(self.output_dir, proj_name, OBJ_DIR_NAME)
            env['PROJ_OBJPIC_DIR'] = path_join(self.output_dir, proj_name, OBJPIC_DIR_NAME)
            if proj_info[OPT_MGC_SCRIPT] != '':
                env['PROJ_MGC_DIR'] = path_join(proj_dir, MGC_DIR_NAME)
                inc_dir, src_dir = self.__get_proj_mgc_dirs(proj_name)
                env['PROJ_MGC_INC_DIR'] = inc_dir
                env['PROJ_MGC_SRC_DIR'] = src_dir
        ret = ''
        for k in env:
            ret += '%s=%s '%(k, env[k])
        return ret

    def __get_proj_mgc_dirs(self, proj_name):
        '''根据项目名称返回mgc产出的inc和src目录，返回值：(inc,src)'''
        return (
            path_join(self.output_dir, MGC_DIR_NAME, proj_name, INC_DIR_NAME),
            path_join(self.output_dir, MGC_DIR_NAME, proj_name, SRC_DIR_NAME)
        )

    def __get_proj_code_dirs(self, proj_name):
        '''根据项目名称返回项目代码inc和src目录，返回值：(inc,src)'''
        proj_info = self.conf[OPT_PROJECTS][proj_name]
        return (
            path_join(proj_info[OPT_PROJ_DIR], INC_DIR_NAME),
            path_join(proj_info[OPT_PROJ_DIR], SRC_DIR_NAME)
        )

    def __update_conf_md5(self):
        def set_default(obj):
            if isinstance(obj, set):
                return list(obj).sort()
            raise TypeError

        proj_md5s = {}
        conf = copy.deepcopy(self.conf)
        # 计算各个项目配置的md5，并更新
        for proj_name in conf[OPT_PROJECTS]:
            proj_info = conf[OPT_PROJECTS][proj_name]
            js_str = json.dumps(proj_info, indent=0, separators=(',', ':'), default=set_default)
            md5_str = hashlib.md5(js_str.encode('utf-8')).hexdigest()
            proj_md5s[proj_name] = md5_str
            g_db.set_proj_conf_md5(proj_name, md5_str)
        # 去掉projects字段，计算剩余部分的md5，并更新
        conf.pop(OPT_PROJECTS)
        js_str = json.dumps(conf, indent=0, separators=(',', ':'), default=set_default)
        global_md5_str = hashlib.md5(js_str.encode('utf-8')).hexdigest()
        g_db.set_global_conf_md5(global_md5_str)

    def __get_proj_inc_args(self, proj_name) -> str:
        '''获取一个项目构建时所需的所有-I 参数（尾不含空格）'''
        # 引用目录包括：
        # 1. 解决方案目录
        # 2. mgc产出的目录（依赖的项目有mgc）
        # 3. remote目录（依赖的项目是remote）
        # 4. 项目自己的各种inc目录：
        #   mgc产出的目录中，项目自己的inc目录（如果有）
        #   remote目录中，项目自己的inc目录（如果有）
        # 5. 项目额外包含的
        dir_names = set(['.'])  # [1]
        proj_names = list(self.conf[OPT_PROJECTS][proj_name][OPT_DEPENDENCY])
        proj_names.append(proj_name)
        mgc_dir = path_join(self.output_dir, MGC_DIR_NAME)
        # remote_dir = path_join(self.output_dir, REMOTE_DIR_NAME)
        for name in self.conf[OPT_PROJECTS][proj_name][OPT_DEPENDENCY]: # [2, 3]
            proj_info = self.conf[OPT_PROJECTS][name]
            if proj_info[OPT_MGC_SCRIPT] != '':
                dir_names.add(mgc_dir)
            # if 是远程项目:
            #    dir_names.add(remote_dir)
            #    dir_names.add(path_join(remote_dir, proj_name, INC_DIR_NAME))
        # [4]
        proj_info = self.conf[OPT_PROJECTS][proj_name]
        proj_dir = proj_info[OPT_PROJ_DIR]
        proj_inc_dir = path_join(proj_dir, INC_DIR_NAME)
        proj_mgc_inc_dir = path_join(mgc_dir, proj_name, INC_DIR_NAME)
        # proj_remote_inc_dir = path_join(remote_dir, proj_name, INC_DIR_NAME)
        dir_names.add(proj_inc_dir)
        if proj_info[OPT_MGC] != {}:
            dir_names.add(proj_mgc_inc_dir)
        # if proj_info is remote:
        #     dir_names.add(proj_remote_inc_dir)
        # [5]
        if len(proj_info[OPT_EXTRA_INC_DIRS]) != 0: 
            dir_names.update(proj_info[OPT_EXTRA_INC_DIRS]) 
        ret = ''
        for name in dir_names:
            ret += ' -I ' + name
        return ret
    
    def __gen_compile_args_str(self, proj_info) -> tuple:
        '''
        生成传给C/CXX编译时的命令行参数字符串

        Return: (c_compile_options, cpp_compile_options)'''
        compile_opt = proj_info[OPT_BUILD_OPTIONS]
        c_compile_args = ''
        cpp_compile_args = ''
        if compile_opt[OPT_C_WARNING] != '':
            c_compile_args += ' ' + compile_opt[OPT_C_WARNING]
        if compile_opt[OPT_CXX_WARNING] != '':
            cpp_compile_args += ' ' + compile_opt[OPT_CXX_WARNING]
        if compile_opt[OPT_C_STANDARD] != '':
            c_compile_args += ' ' + compile_opt[OPT_C_STANDARD]
        if compile_opt[OPT_CXX_STANDARD] != '':
            cpp_compile_args += ' ' + compile_opt[OPT_CXX_STANDARD]
        if compile_opt[OPT_OPT_LEVEL] != '':
            c_compile_args += ' ' + compile_opt[OPT_OPT_LEVEL]
        if compile_opt[OPT_EXTRA_FLAGS] != '':
            cpp_compile_args += ' ' + compile_opt[OPT_EXTRA_FLAGS]
        if compile_opt[OPT_ADD_DEBUG_SYMBOL] == True:
            c_compile_args += ' -D DEBUG=1 -g'
            cpp_compile_args += ' -D DEBUG=1 -g'
        # if compile_opt[OPT_DEFINE] != {}:
        #     def_str = ''
        #     for name in compile_opt[OPT_DEFINE]:
        #         def_str += ' -D %s=%s'%(name, compile_opt[OPT_DEFINE][name])
        #     c_compile_args += def_str
        #     cpp_compile_args += def_str
        return (c_compile_args, cpp_compile_args)

    def __gen_file_rely_analyze_cmds(self):
        '''生成solution中所有用于分析文件依赖的所有指令
        
        Return: ([file_name], [cmd])'''
        cmds = []
        all_src_file_names = []
        for proj_name in self.conf[OPT_PROJECTS]:
            proj_info = self.conf[OPT_PROJECTS][proj_name]
            c_compile_args, cpp_compile_args = self.__gen_compile_args_str(proj_info)
        
            c_compiler = proj_info[OPT_BUILD_OPTIONS][OPT_C_COMPILER]
            cpp_compiler = proj_info[OPT_BUILD_OPTIONS][OPT_CXX_COMPILER]

            src_file_names = self.files.get_src_files(proj_name)
            inc_args = self.__get_proj_inc_args(proj_name)
            for file_name in src_file_names:
                if self.lazy_judge.is_file_need_analyze(proj_name, file_name) == False:
                    g_db.set_file_rely_by_history(file_name)
                    continue
                suffix = file_name.split('.')[-1]
                compiler = None
                if suffix in self.cpp_suffix:
                    compiler = cpp_compiler
                    compile_args = cpp_compile_args
                elif suffix in self.c_suffix:
                    compiler = c_compiler
                    compile_args = c_compile_args
                else:
                    continue
                cmd = compiler + ' -MM' + compile_args + ' ' + inc_args + ' ' + file_name
                cmds.append(cmd)
                all_src_file_names.append(file_name)
        return (all_src_file_names, cmds)

    def __gen_projs_build_cmds(self, proj_names) -> dict:
        '''生成构建所有项目的命令DAG
        
        Return: {proj_name:[[(file, cmd) ...] ...]} [()...]中的所有命令可以并发执行'''
        proj_build_cmds = {}
        for proj_name in proj_names:
            proj_info = self.conf[OPT_PROJECTS][proj_name]
            if proj_info[OPT_BUILD_SCRIPT] != '':
                env = self.__gen_bash_env(proj_name)
                sh = proj_info[OPT_BUILD_OPTIONS][OPT_SCRIPT_SHELL]
                script = path_join(proj_info[OPT_PROJ_DIR], proj_info[OPT_BUILD_SCRIPT])
                cmd = '%s %s %s'%(env, sh, script)
                proj_build_cmds[proj_name] = [[('', cmd)]]
                continue
            proj_obj_dir = path_join(self.output_dir, proj_name, OBJ_DIR_NAME)
            proj_objpic_dir = path_join(self.output_dir, proj_name, OBJPIC_DIR_NAME)
            cur_proj_build_cmds = []
            # 根据产出类型判断构建所需动作
            compile = compile_pic = False
            build_static = link_dynamic = False
            link_exe = link_mexe = False
            if proj_info[OPT_TYPE] in set(['lib', 'lib_dll']):
                compile = build_static = True
            if proj_info[OPT_TYPE] in set(['dll', 'lib_dll']):
                compile_pic = link_dynamic = True
            if proj_info[OPT_TYPE] == 'exe':
                compile = link_exe = True
            if proj_info[OPT_TYPE] == 'mexe':
                compile = link_mexe = True
            # before_build脚本
            if proj_info[OPT_BEFORE_BUILD_PROJ_SCRIPT] != '':
                env = self.__gen_bash_env(proj_name)
                sh = proj_info[OPT_BUILD_OPTIONS][OPT_SCRIPT_SHELL]
                script = path_join(proj_info[OPT_PROJ_DIR], proj_info[OPT_BEFORE_BUILD_PROJ_SCRIPT])
                cmd = '%s %s %s'%(env, sh, script)
                cur_proj_build_cmds.append([('', cmd)])
            # 编译
            compile_cmd_count = 0
            if compile == True or compile_pic == True:
                cmds = self.__genCompileCmds(proj_name, proj_info, proj_obj_dir, proj_objpic_dir, compile_pic)
                cur_proj_build_cmds.append(cmds)
                compile_cmd_count = len(cmds)
            cmds = []
            # 链接
            dep_proj_names = self.proj_dag.get_dep_nodes(proj_name)
            if self.lazy_judge.is_proj_need_link(proj_name, dep_proj_names, compile_cmd_count):
                # 静态库或者动态库
                tmp_cmds = self.__genBuildLibAndDllCmds(proj_name, proj_info, build_static, link_dynamic)
                cmds.extend(tmp_cmds)
                # 可执行文件
                tmp_cmds = self.__genLinkExeAndMexeCmds(proj_name, proj_info, link_exe, link_mexe)
                cmds.extend(tmp_cmds)
                cur_proj_build_cmds.append(cmds)
            # after_build脚本
            if proj_info[OPT_AFTER_BUILD_PROJ_SCRIPT] != '':
                env = self.__gen_bash_env(proj_name)
                sh = proj_info[OPT_BUILD_OPTIONS][OPT_SCRIPT_SHELL]
                script = path_join(proj_info[OPT_PROJ_DIR], proj_info[OPT_AFTER_BUILD_PROJ_SCRIPT])
                cmd = '%s %s %s'%(env, sh, script)
                cur_proj_build_cmds.append([('', cmd)])
            proj_build_cmds[proj_name] = cur_proj_build_cmds
        return proj_build_cmds

    def __genCompileCmds(self, proj_name, proj_info, proj_obj_dir, proj_objpic_dir, is_pic) -> list:
        '''生成一个项目所需的编译命令，返回[cmd]，并记录生成的所有.o文件名
        无需编译时，返回空[]'''
        cmds = []
        proj_obj_fnames = []
        files = self.files.get_src_files(proj_name)
        inc_args = self.__get_proj_inc_args(proj_name)
        c_compile_args_str, cpp_compile_args_str = self.__gen_compile_args_str(proj_info)
        c_compiler = proj_info[OPT_BUILD_OPTIONS][OPT_C_COMPILER]
        cpp_compiler = proj_info[OPT_BUILD_OPTIONS][OPT_CXX_COMPILER]
        for fname in files:
            suffix = fname.split('.')[-1]
            # src_inner_name = fname[fname.find(SRC_DIR_NAME) + 4 :] # 截取项目目录下'src'后面的部分，obj与src保持一致时使用
            if suffix not in SRC_FILE_SUFFIX:
                continue
            if self.lazy_judge.is_file_need_compile(proj_name, fname) == True:
                g_db.set_file_success(fname, False)
            else:
                g_db.set_file_success(fname, True)
                continue
            if is_pic == False:
                output_file_name = path_join(proj_obj_dir, os.path.basename(fname) + '.o')
                if suffix in CXX_FILE_SUFFIX:
                    cmd = '%s -c%s%s %s -o %s'%(
                        cpp_compiler, inc_args, cpp_compile_args_str,
                        fname, output_file_name
                    )
                elif suffix in C_FILE_SUFFIX:
                    cmd = '%s -c %s %s %s -o %s'%(
                        c_compiler, inc_args, c_compile_args_str,
                        fname, output_file_name
                    )
                cmds.append((fname, cmd))
                proj_obj_fnames.append(output_file_name)
            if is_pic == True:
                output_file_name = path_join(proj_objpic_dir, os.path.basename(fname) + '.o')
                if suffix in CXX_FILE_SUFFIX:
                    cmd = '%s -c %s %s %s -o %s'%(
                        cpp_compiler, inc_args, cpp_compile_args_str,
                        fname, output_file_name
                    )
                elif suffix in C_FILE_SUFFIX:
                    cmd = '%s -c %s %s %s -o %s'%(
                        c_compiler, inc_args, c_compile_args_str,
                        fname, output_file_name
                    )
                cmds.append((fname, cmd))
        self.proj_obj_files[proj_name] = proj_obj_fnames # 链接mexe要用
        return cmds
    
    def __genBuildLibAndDllCmds(self, proj_name, proj_info, static_flag, dynamic_flag) -> list:
        cmds = []
        build_options = proj_info[OPT_BUILD_OPTIONS]
        lib_builder = build_options[OPT_LIB_BUILDER]
        dll_builder = build_options[OPT_DLL_BUILDER]
        if static_flag == True:
            obj_dir = path_join(self.output_dir, proj_name, OBJ_DIR_NAME)
            lib_name = self.__genOutputLibName(proj_name)
            cmd = '%s -cr %s %s/*'%(lib_builder, lib_name, obj_dir)
            cmds.append(('', cmd))
        if dynamic_flag == True:
            # ???是否需要链接参数？？？
            objpic_dir = path_join(self.output_dir, proj_name, OBJPIC_DIR_NAME)
            dep_libs = self.__genDepLibsStr(proj_name)
            dll_name = self.__genOutputDllName(proj_name)
            cmd = '%s -shared %s/* %s -o %s %s'%(dll_builder, objpic_dir, dep_libs, dll_name, build_options[OPT_LINK_OPTION])
            cmds.append(('', cmd))
        return cmds

    def __genLinkExeAndMexeCmds(self, proj_name, proj_info, exe_flag, mexe_flag) -> list:
        cmds = []
        dep_libs = self.__genDepLibsStr(proj_name)
        build_options = proj_info[OPT_BUILD_OPTIONS]
        linker = build_options[OPT_LINKER]
        link_options = build_options[OPT_LINK_OPTION]
        if exe_flag == True:
            obj_dir = path_join(self.output_dir, proj_name, OBJ_DIR_NAME)
            exe_name = self.__genOutputExeName(proj_name)
            cmd = '%s %s/* %s -o %s %s'%(
                linker, obj_dir, dep_libs, exe_name, link_options
            )
            cmds.append(('', cmd))
        elif mexe_flag == True:
            obj_fnames = self.proj_obj_files[proj_name]
            for obj_name in obj_fnames:
                base_name = os.path.basename(obj_name).split('.')[0]
                exe_name = self.__genOutputExeName(proj_name, base_name)
                cmd = '%s %s %s -o %s %s'%(
                    linker, obj_name, dep_libs, exe_name, link_options
                )
                cmds.append(('', cmd))
        return cmds

    # def __genIncArgsStr(self, proj_name:str) -> str:
    #     inc_dirs = set(['.'])
        
    #     ret = '-I . -I %s/inc -I %s/mgc'%(proj_name, self.output_dir)
    #     if self.conf[OPT_PROJECTS][proj_name][OPT_MGC_SCRIPT] != '':
    #         ret += ' -I %s/%s/inc'%(self.output_dir, proj_name)
    #     return ret

    def __genDepLibsStr(self, proj_name:str) -> str:
        '''生成链接时所依赖的库，拼成的字符串，空格分开'''
        ret = ''
        dep_proj_names = self.proj_dag.get_dep_nodes(proj_name)
        for proj_name in dep_proj_names:
            proj_out_type = self.conf[OPT_PROJECTS][proj_name][OPT_TYPE]
            if 'lib' in proj_out_type:
                ret += self.__genOutputLibName(proj_name) + ' '
            elif 'dll' in proj_out_type:
                ret += self.__genOutputDllName(proj_name) + ' '
        return ret

    def __genOutputLibName(self, proj_name):
        return path_join(self.output_dir, proj_name, 'lib%s.a'%(proj_name))

    def __genOutputDllName(self, proj_name):
        return path_join(self.output_dir, proj_name, 'lib%s.so'%(proj_name))

    def __genOutputExeName(self, proj_name, base_name=''):
        if base_name == '':
            base_name = proj_name
        return path_join(self.output_dir, proj_name, '%s.exe'%(base_name))
